package com.linkallcloud.core.domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.springframework.beans.BeanUtils;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.annotation.JSONField;
import com.linkallcloud.core.castor.Castors;
import com.linkallcloud.core.dto.Archive;
import com.linkallcloud.core.dto.ArchiveVo;
import com.linkallcloud.core.dto.CompareNameValue;
import com.linkallcloud.core.dto.NameValue;
import com.linkallcloud.core.dto.Sid;
import com.linkallcloud.core.dto.Trace;
import com.linkallcloud.core.dto.born.DefaultDtoBorning;
import com.linkallcloud.core.dto.born.DtoBorning;
import com.linkallcloud.core.dto.born.DtoBorns;
import com.linkallcloud.core.enums.FieldType;
import com.linkallcloud.core.enums.FlowStepType;
import com.linkallcloud.core.enums.Status;
import com.linkallcloud.core.lang.Mirror;
import com.linkallcloud.core.lang.Strings;
import com.linkallcloud.core.log.Log;
import com.linkallcloud.core.log.Logs;
import com.linkallcloud.core.vo.Vo;

public abstract class Domain implements IDomain {
    private static final long serialVersionUID = 5910672863713144935L;

    @JSONField(serialize = false)
    protected static Log log = Logs.get();

    /**
     * 实体编号（唯一标识）
     */
    protected Long id;
    protected String uuid;

    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    protected Date createTime; // 创建时间
    // protected PK createUserId;// 创建者id

    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    protected Date updateTime; // 更新时间
    // protected PK updateUserId;// 更新者id

    protected Integer status; // 状态

    private String files;// 上传的文件

    // 以下非数据库字段
    private Trace t;
    private List<Archive> archives;
    private String nonce;
    // 是否需要记录历史（FlowStep）:0：状态，1：属性修改，其它：不记录。参见com.linkallcloud.core.enums.FlowStepType
    private Integer flowStep;
    // 流程定义code
    private String twfDefineCode;
    // 业务自定义字段//String groupName, String fieldName, String fieldValue
    // @JSONField(serialize = false)
    private List<NameValue> twfBaseInfo;
    // 流程图参数字段
    // @JSONField(serialize = false)
    private List<NameValue> twfChartArgs;

    public Domain() {
        super();
        this.status = STATUS_NORMAL;
        this.uuid = generateUuid();
        this.createTime = new Date();
    }

    public Domain(Long id) {
        this();
        this.id = id;
        this.uuid = generateUuid();
    }

    public Domain(Long id, String uuid) {
        this();
        this.id = id;
        this.uuid = uuid;
    }

    public <V extends Vo> Domain(V vo) {
        this();
        BeanUtils.copyProperties(vo, this);
    }

    public <V extends Vo> V toVo(Class<V> classV) {
        Mirror<V> mirror = Mirror.me(classV);
        try {
            V vo = mirror.born();
            BeanUtils.copyProperties(this, vo);
            return vo;
        } catch (Throwable e) {
        }
        return null;
    }

    /**
     * Domain 转换成 Vo,根据提供的方法和构造参数，自动查找合适的静态构造方法或者构造器.
     *
     * @param domain
     * @param classV
     * @return
     */
    public static <E extends Domain, V extends Vo> V domain2Vo(E domain, Class<V> classV) {
        return Domain.domain2Vo(domain, classV, null, new Object[] { domain });
    }

    /**
     * Domain 转换成 Vo,根据提供的方法和构造参数，自动查找合适的静态构造方法或者构造器.
     *
     * @param domain
     * @param classV
     * @param methodName
     * @param methodArgs
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <E extends Domain, V extends Vo> V domain2Vo(E domain, Class<V> classV, String methodName,
            Object[] methodArgs) {
        if (domain != null && classV != null) {
            if (matchDomainArg(methodArgs, domain)) {
                Class<?>[] createArgTypes = Mirror.evalToTypes(methodArgs);
                DtoBorning borning = DtoBorns.evalBorning(classV, domain, methodName, createArgTypes);
                if (null != borning) {
                    if (borning instanceof DefaultDtoBorning) {
                        try {
                            return domain.toVo(classV);
                        } catch (Throwable e1) {
                            log.error(e1.getMessage(), e1);
                        }
                    } else {
                        try {
                            return (V) borning.born(methodArgs);
                        } catch (Throwable e) {
                            log.error(e.getMessage(), e);
                        }
                    }
                }
            } else {
                Mirror<V> mirror = Mirror.me(classV);
                try {
                    V vo = mirror.born();
                    BeanUtils.copyProperties(domain, vo);
                    return vo;
                } catch (Throwable e) {
                }
            }
        }
        return null;
    }

    /**
     * Vo 转换成 Domain,根据提供的方法和构造参数，自动查找合适的静态构造方法或者构造器.
     *
     * @param vo
     * @param classD
     * @return
     */
    public static <E extends Domain, V extends Vo> E vo2Domain(V vo, Class<E> classD) {
        return Domain.vo2Domain(vo, classD, null, new Object[] { vo });
    }

    /**
     * Vo 转换成 Domain,根据提供的方法和构造参数，自动查找合适的静态构造方法或者构造器.
     *
     * @param vo
     * @param classD
     * @param methodName
     * @param methodArgs
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <E extends Domain, V extends Vo> E vo2Domain(V vo, Class<E> classD, String methodName,
            Object[] methodArgs) {
        if (vo != null && classD != null) {
            if (matchDomainArg(methodArgs, vo)) {
                Class<?>[] createArgTypes = Mirror.evalToTypes(methodArgs);
                DtoBorning borning = DtoBorns.evalBorning(classD, vo, methodName, createArgTypes);
                if (null != borning) {
                    try {
                        return (E) borning.born(methodArgs);
                    } catch (Throwable e) {
                        log.error(e.getMessage(), e);
                    }
                }
            } else {
                Mirror<E> mirror = Mirror.me(classD);
                try {
                    E domain = mirror.born();
                    BeanUtils.copyProperties(vo, domain);
                    return domain;
                } catch (Throwable e) {
                }
            }
        }
        return null;
    }

    /**
     * 检查参数中是否匹配到参数obj
     *
     * @param args
     * @param obj
     * @return true or false
     */
    private static boolean matchDomainArg(Object[] args, Object obj) {
        if (null == obj || null == args || args.length <= 0) {
            return false;
        }
        for (Object arg : args) {
            if (arg == obj) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Long getId() {
        return id;
    }

    @Override
    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public String getUuid() {
        return uuid;
    }

    @Override
    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    @Override
    public Date getCreateTime() {
        return createTime;
    }

    @Override
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public Date getUpdateTime() {
        return updateTime;
    }

    @Override
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    @Override
    public Integer getStatus() {
        return status;
    }

    @Override
    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public boolean isValid() {
        return this.status == 0;
    }

    @Override
    public String generateUuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    @Override
    public boolean equals(Object obj) {
        if (null == obj) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (!getClass().equals(obj.getClass())) {
            return false;
        }
        IDomain that = (IDomain) obj;
        return null == this.getId() ? false : this.getId().equals(that.getId());
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

    public Sid sid() {
        return new Sid(this.getId(), this.getUuid());
    }

    public Trace getT() {
        return t;
    }

    public void setT(Trace t) {
        this.t = t;
    }

    public void traceApp(Trace t, Sid app) {
        if (t != null) {
            this.t = t;
        }
        this.t.traceFss(app, this);
    }

    @Override
    public void dataMasking(String nonce) {
    }

    @Override
    public void reenforce(String nonce) {
    }

    public List<Archive> getArchives() {
        return archives;
    }

    public void setArchives(List<Archive> archives) {
        this.archives = archives;
    }

    public List<Archive> addArchive(Archive a) {
        if (this.archives == null) {
            this.archives = new ArrayList<>();
        }
        this.archives.add(a);
        return this.archives;
    }

    public String getNonce() {
        return nonce;
    }

    public void setNonce(String nonce) {
        this.nonce = nonce;
    }

    public Domain clone() {
        try {
            Domain newObj = this.getClass().getDeclaredConstructor().newInstance();
            BeanUtils.copyProperties(this, newObj);
            return newObj;
        } catch (Throwable e) {
            return null;
        }
    }

    @JSONField(serialize = false)
    public String getFiles() {
        return files;
    }

    public void setFiles(String files) {
        this.files = files;
    }

    public List<ArchiveVo> getFilesArr() {
        return Strings.isBlank(files) ? Collections.emptyList() : JSON.parseArray(files, ArchiveVo.class);
    }

    public void setFilesArr(List<ArchiveVo> files) {
        this.files = (files != null && !files.isEmpty()) ? JSON.toJSONString(files) : "[]";
    }

    public List<CompareNameValue> filterFields(String objPropType) {
        List<CompareNameValue> result = new ArrayList<CompareNameValue>();
        result.add(new CompareNameValue("status", this.getStatus() + "", "状态", FieldType.Select.getCode(),
                JSON.toJSONString(statusOptions())));
        return result;
    }

    public List<CompareNameValue> composeFilterFields(String objPropType, Domain newEntity) {
        List<CompareNameValue> result = this.filterFields(objPropType);
        if (this.getClass().equals(newEntity.getClass())) {
            List<CompareNameValue> resultNew = newEntity.filterFields(objPropType);
            if (result != null && !result.isEmpty() && resultNew != null && !resultNew.isEmpty()) {
                for (int i = 0; i < result.size(); i++) {
                    CompareNameValue r = result.get(i);
                    r.setNewVal(resultNew.get(i).getValue());
                }
            }
        }
        return result;
    }

    public List<NameValue> statusOptions() {
        List<NameValue> options = new ArrayList<>();
        options.add(new NameValue("0", "正常"));
        options.add(new NameValue("1", "锁定"));
        options.add(new NameValue("8", "禁用"));
        options.add(new NameValue("9", "删除"));
        return options;
    }

    public Integer getFlowStep() {
        return flowStep;
    }

    public void setFlowStep(Integer flowStep) {
        this.flowStep = flowStep;
    }

    public String getTwfDefineCode() {
        return twfDefineCode;
    }

    public void setTwfDefineCode(String twfDefineCode) {
        this.twfDefineCode = twfDefineCode;
    }

    public List<NameValue> getTwfBaseInfo() {
        return twfBaseInfo;
    }

    public void putTwfBaseInfo(String groupName, String fieldName, String fieldValue) {
        if (twfBaseInfo == null) {
            twfBaseInfo = new ArrayList<NameValue>();
        }
        twfBaseInfo.add(new NameValue(groupName, fieldName, fieldValue));
    }

    public void setTwfBaseInfo(List<NameValue> twfBaseInfo) {
        this.twfBaseInfo = twfBaseInfo;
    }

    public List<NameValue> getTwfChartArgs() {
        return twfChartArgs;
    }

    public void putTwfChartArg(String key, String value, String title) {
        if (twfChartArgs == null) {
            twfChartArgs = new ArrayList<NameValue>();
        }
        twfChartArgs.add(new NameValue(key, value, title));
    }

    public void setTwfChartArgs(List<NameValue> twfChartArgs) {
        this.twfChartArgs = twfChartArgs;
    }

    protected FlowStep newFlowStep() {
        return new FlowStep();
    }

    protected FlowStep createFlowStep4Status(String caption, Domain old) {
        FlowStep result = newFlowStep();
        result.setFlowType(FlowStepType.Status.getCode());
        result.setObjType(this.getClass().getSimpleName());
        result.setObjId(this.getId());
        result.setObjUuid(this.getUuid());
        result.setObjNewStatus(this.getStatus());
        if (this.getId() == null) {
            result.setCaption(Strings.isBlank(caption) ? "创建" : caption);
            result.setObjOldStatus(Status.Created.getCode());
        } else {
            result.setCaption(Strings.isBlank(caption) ? "修改" : caption);
            result.setObjOldStatus(old == null ? Status.Created.getCode() : old.getStatus());
        }
        return result;
    }

    protected FlowStep createFlowStep4Modify(String objPropType, String caption, Domain old) {
        FlowStep result = newFlowStep();
        result.setFlowType(FlowStepType.Prop.getCode());
        result.setObjPropType(objPropType);
        result.setCaption(Strings.isBlank(caption) ? "修改" : caption);
        result.setObjType(this.getClass().getSimpleName());
        result.setObjId(this.getId());
        result.setObjUuid(this.getUuid());
        return result;
    }

    public FlowStep toFlowStep4Status(String caption, Domain old, Map<String, Object> attrs) {
        FlowStep fs = createFlowStep4Status(caption, old);

        if (attrs.containsKey("id")) {
            String phone = (String) attrs.get("userPhone");
            String remark = (String) attrs.get("remark");
            Long cuserId = Castors.me().castTo(attrs.get("id"), Long.class);
            fs.setOperator(cuserId, (String) attrs.get("name"), phone, remark);
        }

        fs.setTwfArgs(this);

        return fs;
    }

    public FlowStep toFlowStep4Modify(String objPropType, String caption, Domain old, Map<String, Object> attrs) {
        FlowStep fs = createFlowStep4Modify(objPropType, caption, old);

        if (old != null) {
            List<CompareNameValue> ffs = old.composeFilterFields(objPropType, this);
            if (ffs != null && !ffs.isEmpty()) {
                fs.setObjCompareJson(JSON.toJSONString(ffs));
            }
        } else {
            List<CompareNameValue> ffs = filterFields(objPropType);
            if (ffs != null && !ffs.isEmpty()) {
                fs.setObjCompareJson(JSON.toJSONString(ffs));
            }
        }

        if (attrs.containsKey("id")) {
            String phone = (String) attrs.get("userPhone");
            String remark = (String) attrs.get("remark");
            Long cuserId = Castors.me().castTo(attrs.get("id"), Long.class);
            fs.setOperator(cuserId, (String) attrs.get("name"), phone, remark);
        }

        fs.setTwfArgs(this);

        return fs;
    }

}
